<?php
namespace App\Models\Helper;

use App\Models\Common\IdBuilderConf;
use Illuminate\Support\Facades\Redis;

/**
 *  ID生成器通用类
 *
 * @package App\Models\Helper
 * @author Liu Sinian
 * @date 2023-04-20
 */
class IdBuilder
{
//    const KEY_BUILDER = 'XMLY_ID_BUILDER_%s';
    const KEY_BUILDER = 'XMLY_ID_BUILDER';
    const FILE_PATH = '/Common/IdBuilderConf.php';
    const TAG_ID_BUILDER = 'ID_BUILDER';

    private static $_instance;
    private $_allIdBuilders = [];

    protected $idBuilderConf;
    public function __construct()
    {
        $this->idBuilderConf = new IdBuilderConf();
    }

    /**
     * 获取创建的实例(单例模式)
     *
     * @return IdBuilder
     */
    public static function getInstance()
    {
        if(!self::$_instance){
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    /**
     * @Desc:快速生成ID的方法(配置对应的规则)
     * @param $key
     * @return array|string|string[]|null
     * @throws \Exception
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:51
     */
    public function createId($key)
    {
        // 获取配置文件, 改用对象获取(解决队列获取配置文件的问题)
        $idBuilderConf = $this->idBuilderConf::getConf();
        // 不存在,需要先去IdBuilderConf配置
        if(!isset($idBuilderConf[$key])){
            throw new \Exception('未定义的编码生成器标识'.$key);
        }
        $format = $idBuilderConf[$key];
        return $this->generate($key, $format);
    }

    /**
     * @Desc:ID 生成器实现(prefix(可选) + [Ymd] + [5])
     * @param $key
     * @param $format
     * @return array|string|string[]|null
     * @throws \Exception
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:51
     */
    public function generate($key, $format = '')
    {
        $eventKey=$key;
        if(empty($key)){
            throw new \Exception('未给定编码生成规则！');
        }

        // 根据配置的日期,作为存储key的一部分, 比如:若是年月日, 则每天的key会不一样
        if(preg_match('/\[([a-zA-Z]+)\]/', $format, $m)){
            $key = sprintf('%s_%s', date($m[1]), $key);
        }

        // 匹配format里面的固定格式, 时间格式[Ymd], e.g 20200822
        $genId = preg_replace_callback('/\[([a-zA-Z]+)\]/', function($m){
            return isset($m[1])? date($m[1]): '';
        }, $format);

        // 匹配format里面的固定格式, 几位自增数[5], 不足补0, e.g 00001
        $genId = preg_replace_callback('/\[([0-9]+)\]/', function($m) use ($key){
            return isset($m[1])? $this->_getMaxId($key, $m[1]): '';
        }, $genId);

        // 检查生成的编码是否合法(只能由大小写字母、数字组成)
        if(!preg_match('/^[A-Za-z0-9]+$/', $genId)){
            throw new \Exception('编码不合法！genId：'. $genId);
        }
//        // 根据场景，增加随机数
//        $eventArrs = ['ORDER_ID', 'IOSTOCK_ORDER_BN', 'DOCTOER_BN', 'INVENTORY_BN'];
//        if (in_array($eventKey, $eventArrs)) {
//            $rand = $this->build('numeric', 3);
//            $genId = $genId.$rand;
//        }

        return $genId;
    }

    /**
     * @Desc:生成全球唯一码
     * @return string
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:51
     */
    public static function guid()
    {
        $charId = strtoupper(md5(uniqid(mt_rand(), true)));
        $uuid = substr($charId, 0, 8) . substr($charId, 8, 4) . substr($charId, 12, 4) . substr($charId, 16, 4) . substr($charId, 20, 12);
        return md5($uuid);
    }

    /**
     * @Desc:获取最大id
     * @param $key
     * @param $len
     * @return int|string
     * @throws \Exception
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:51
     */
//    private function _getMaxId($key, $len = 0)
//    {
//        // 存储的key
//        $storeKey = sprintf(self::KEY_BUILDER, $key);
////        $storeKey = self::KEY_BUILDER;
//        Redis::Incr($storeKey);
//        $inc = Redis::Get($storeKey);
//        // // 是否已存在对应的KEY
//        // $inc = intval($cacheData) + 1;
//        // // 存储ID, 不设置过期时间(系统默认为9999999999)
//        // Redis::Hset($storeKey, $key, $inc);
//
//         if (0 >= $len) {
//             return $inc;
//         }
//        $inc = str_repeat('0', $len) . strval($inc);
//
//        return substr($inc, -$len);
//    }
//    private function _getMaxId($key, $len = 0)
//    {
//        // 存储的key
////        $storeKey = sprintf(self::KEY_BUILDER, $key);
//        $storeKey = self::KEY_BUILDER;
//        $cacheData = Redis::Hget($storeKey, $key);
//        // 是否已存在对应的KEY
//        $inc = intval($cacheData) + 1;
//        // 存储ID, 不设置过期时间(系统默认为9999999999)
//        Redis::Hset($storeKey, $key, $inc);
//
//        if (0 >= $len) {
//            return $inc;
//        }
//        $inc = str_repeat('0', $len) . strval($inc);
//
//        return substr($inc, -$len);
//    }
    private function _getMaxId($key, $len = 0)
    {
        // 存储的key
//        $storeKey = sprintf(self::KEY_BUILDER, $key);
        $storeKey = self::KEY_BUILDER;
        $cacheData = Redis::Hget($storeKey, $key);
        // 是否已存在对应的KEY
        $inc = intval($cacheData) + 1;
        // 存储ID, 不设置过期时间(系统默认为9999999999)
        $expireTime = mktime(23, 59, 59, date("m"), date("d"), date("Y")); // 设置当天有效
//        $seconds = 86400;
        Redis::Hset($storeKey, $key, $inc);
        Redis::expireAt($storeKey, $expireTime);
//        Redis::expire($storeKey, $seconds);
        if (0 >= $len) {
            return $inc;
        }
        $inc = str_repeat('0', $len) . strval($inc);

        return substr($inc, -$len);
    }

    /**
     * @Desc:用于c:f后恢复对应的Id builder key
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:52
     */
    public function reStoreIdBuilderKeys()
    {
        foreach ($this->_allIdBuilders as $k => $v){
            $this->cache->save($v, $k, [self::TAG_ID_BUILDER]);
        }
    }


    /**
     * @Desc:能用的随机数生成
     * @param string $type 类型 alpha/alnum/numeric/nozero/unique/md5/encrypt/sha1
     * @param int $len 长度
     * @return string|void
     * @author: Liu Sinian
     * @Time: 2023/4/20 16:52
     */
    public function build($type = 'alnum', $len = 8)
    {
        switch ($type) {
            case 'alpha':
            case 'alnum':
            case 'numeric':
            case 'nozero':
                switch ($type) {
                    case 'alpha':
                        $pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                        break;
                    case 'alnum':
                        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
                        break;
                    case 'numeric':
                        $pool = '0123456789';
                        break;
                    case 'nozero':
                        $pool = '123456789';
                        break;
                }
                return substr(str_shuffle(str_repeat($pool, ceil($len / strlen($pool)))), 0, $len);
            case 'unique':
            case 'md5':
                return md5(uniqid(mt_rand()));
            case 'encrypt':
            case 'sha1':
                return sha1(uniqid(mt_rand(), true));
        }
    }

}